package com.capsulode.excel;

import com.capsulode.excel.conversion.ConverterManager;
import com.capsulode.excel.domain.ExcelImportResult;
import com.capsulode.excel.exception.InvalidTemplateException;
import com.capsulode.excel.header.CellConsumer;
import com.capsulode.excel.header.DefaultHeader;
import com.capsulode.excel.header.SimpleHeader;
import com.capsulode.excel.sheetreader.*;
import com.capsulode.excel.util.ExcelUtil;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

public class ExcelImporter {
    private final Workbook workbook;
    private final Map<String, SheetReader> sheetReaders = new HashMap<>();

    public ExcelImporter(Workbook workbook) {
        this.workbook = workbook;
    }

    public ExcelImporter(InputStream is, String contentType) throws IOException {
        this.workbook = ExcelUtil.guessInstanceFromContentTypeOrFilename(is, contentType);
    }

    /**
     * Register Worksheet import business.
     * <p>
     * <p>This is same as {@link #sheet(String) sheet("Sheet1")}.</p>
     */
    public SimpleBuilder sheet() {
        return sheet("Sheet1");
    }

    /**
     * Register Worksheet import business.
     *
     * @param sheetName which sheet to import.
     */
    public SimpleBuilder sheet(String sheetName) {
        return new SimpleBuilder(sheetName);
    }

    /**
     * Register Worksheet import business.
     * <p>
     * <p>This is same as {@link #sheet(String, RowConsumer) sheet("Sheet1",rowConsumer)}.</p>
     *
     * @param rowConsumer define how to create a JavaBean which represents a row.
     */
    public <R> Builder<R> sheet(RowConsumer<R> rowConsumer) {
        return sheet("Sheet1", rowConsumer);
    }

    /**
     * Register Worksheet import business.
     *
     * @param sheetName   which sheet to import.
     * @param rowConsumer define how to create a JavaBean which represents a row.
     */
    public <R> Builder<R> sheet(String sheetName, RowConsumer<R> rowConsumer) {
        return new Builder<>(sheetName, rowConsumer);
    }


    /**
     * Register Worksheet import business.
     * <p>
     * <p>This is same as {@link #sheet(String, RowConsumer) sheet("Sheet1",rowConsumer)}.</p>
     *
     * @param rowClass annotated JavaBean which wrappers row data.
     */
    public <R> ExcelImporter sheet(Class<R> rowClass) {
        return sheet("Sheet1", null, null, rowClass, null);
    }

    public <R> ExcelImporter sheet(Class<R> rowClass, int from) {
        return sheet("Sheet1", null, from, rowClass, null);
    }

    /**
     * Register Worksheet import business.
     *
     * @param sheetName   which sheet to import.
     * @param rowClass    annotated JavaBean which wrappers row data.
     * @param rowConsumer define how to create the `rowClass`.
     */
    public <R> ExcelImporter sheet(String sheetName, Integer batchSize, Integer from, Class<R> rowClass, RowConsumer<R> rowConsumer) {
        AnnotatedSheetReader<R> sr = new AnnotatedSheetReader<>(rowClass, rowConsumer);
        if (batchSize != null) {
            sr.batchSize(batchSize);
        }
        if (from != null) {
            sr.from(from);
        }
        sheetReaders.put(sheetName, sr);
        return this;
    }

    public ExcelImportResult process(BatchConsumer consumer) {
        Sheet sheet = workbook.getSheetAt(0);
        if (sheet == null) {
            throw new InvalidTemplateException("Excel 文件模版不匹配：无可用工作表");
        }
        Object[] objects = sheetReaders.values().toArray();
        if (objects.length == 0) {
            throw new IllegalStateException("尚未定义工作表");
        }
        SheetReader sheetReader = (SheetReader) objects[0];

        return _process(sheet, sheetReader, consumer);
    }

    public ExcelImportResult process(String sheetName, BatchConsumer consumer) {
        Sheet sheet = workbook.getSheet(sheetName);
        SheetReader sheetReader = sheetReaders.get(sheetName);
        if (sheet == null || sheetReader == null) {
            throw new InvalidTemplateException("Excel 文件模版不匹配：找不到工作表：" + sheetName);
        }
        return _process(sheet, sheetReader, consumer);
    }

    private <R> ExcelImportResult _process(Sheet sheet, SheetReader<R> sheetReader, BatchConsumer<R> consumer) {
        ExcelImportResult result = sheetReader.consume(sheet, consumer);
        ConverterManager.evictThreadLocal();
        return result;
    }


    public class SimpleBuilder {
        private final String name;
        private final SimpleSheetReader<Integer> sheetReader = new SimpleSheetReader<>();

        public SimpleBuilder(String name) {
            this.name = name;
        }

        public SimpleBuilder header(String name) {
            sheetReader.header(new SimpleHeader(name));
            return this;
        }

        public SimpleBuilder batchSize(int chunkSize) {
            sheetReader.batchSize(chunkSize);
            return this;
        }

        public SimpleBuilder from(int rowIndexAtDataBegining) {
            sheetReader.from(rowIndexAtDataBegining);
            return this;
        }

        public ExcelImporter build() {
            sheetReaders.put(name, sheetReader);
            return ExcelImporter.this;
        }
    }

    public class Builder<R> {
        private final String name;
        private final AbstractSheetReader<R> sheetReader;
        private int count = 0;

        public Builder(String name, RowConsumer<R> rowConsumer) {
            this.name = name;
            this.sheetReader = new DefaultSheetReader<>(rowConsumer);
        }

        public <F> Builder<R> header(String name, Class<F> fieldType, CellConsumer<R, F> cellConsumer) {
            DefaultHeader<R, F> header = new DefaultHeader<>(name, fieldType, cellConsumer);
            header.setIndex(count++);
            sheetReader.header(header);
            return this;
        }

        public Builder<R> from(int rowIndexAtDataBegining) {
            sheetReader.from(rowIndexAtDataBegining);
            return this;
        }

        public Builder<R> batchSize(int chunkSize) {
            sheetReader.batchSize(chunkSize);
            return this;
        }

        public ExcelImporter build() {
            sheetReaders.put(name, sheetReader);
            return ExcelImporter.this;
        }
    }
}
